﻿using System;
using System.Collections.Generic;
using System.IO;
using System.IO.IsolatedStorage;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using FITClub.Util;
using Microsoft.Xna.Framework;
using ExternalClasses.Util;

namespace FITClub.Episode1.Controller.AI
{
    /// <summary>
    /// Level wayponts for AI players represented as oriented graph.
    /// </summary>
    public class Waypoints : IEnumerable<WPNode>
    {
        /// <summary>
        /// All waypoints, named graph nodes
        /// </summary>
        public Dictionary<string, WPNode> AllWaypoints { get; private set; }
        public float MaxCharacterDistance = 0.3f;
        private Queue<WPNode> queue;

        public Waypoints(Stream input)
        {
            this.queue = new Queue<WPNode>();
            this.AllWaypoints = new Dictionary<string, WPNode>();
            List<SerializableWPNode> nodes = this.Load(input);
            ReconstructGraph(nodes);
        }

        public Waypoints(string waypointPath)
            : this(TitleContainer.OpenStream(waypointPath))
        {
        }

        public Waypoints()
        {
            this.AllWaypoints = new Dictionary<string, WPNode>();
        }

        public void ReconstructGraph(List<SerializableWPNode> nodes)
        {
            foreach (var item in nodes)
            {
                try
                {
                    this.AllWaypoints.Add(item.Node.Name, item.Node);
                }
                catch (Exception exc)
                {
                    Console.WriteLine(exc);
                }
            }
            foreach (var swp in nodes)
            {
                foreach (var item in swp.SiblingNames)
                {
                    swp.Node.ConnectTo(this.AllWaypoints[item]);
                }
            }
        }

        /// <summary>
        /// Remove node and its incidentions from graph.
        /// </summary>
        /// <param name="name">Name of node to beremoved</param>
        public void Remove(string name)
        {
            WPNode trnode = this.AllWaypoints[name];
            foreach (var item in this.AllWaypoints.Values)
            {
                item.Disconnect(trnode);
            }
            this.AllWaypoints.Remove(name);
        }

        /// <summary>
        /// Prepare for graph traversal.
        /// </summary>
        public void ClearVisits()
        {
            foreach (var item in this)
            {
                item.Visited = TraversalState.Fresh;
                item.Depth = int.MinValue;
                item.Predcessor = null;
            }
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="position"></param>
        /// <param name="bound1"></param>
        /// <param name="bound2"></param>
        public void Find(Vector2 position, out WPNode bound1, out WPNode bound2)
        {
            this.ClearVisits();
            WPNode actual = this.First();
            bound1 = null;
            bound2 = null;
            BFS(position, ref bound1, ref bound2);
#if DEBUG
            System.Diagnostics.Debug.WriteLine("Find result: {0};  {1};  chPos={2}", bound1, bound2, position);
#endif
        }

        public static bool LineIntersectsCircle(Vector2 start, Vector2 end, Vector2 S, float radius)
        {
            return LinePointDistance(start, end, S) <= radius;
        }

        public static float LinePointDistance(Vector2 start, Vector2 end, Vector2 S)
        {
            float dist = Math.Abs((end.X - start.X) * (start.Y - S.Y) - (start.X - S.X) * (end.Y - start.Y));
            float a = end.X - start.X;
            a *= a;
            float b = end.Y - start.Y;
            b *= b;
            return dist / (float)Math.Sqrt(a + b);
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="chPos"></param>
        /// <param name="start"></param>
        /// <param name="end"></param>
        private void BFS(Vector2 chPos, ref WPNode start, ref WPNode end)
        {
            this.ClearVisits();

            WPNode actNode = this.First();
            actNode.Visited = TraversalState.Open;
            actNode.Depth = 0;
            //this.queue.Clear();
            this.queue.Enqueue(actNode);
            Incidention minimalDist = new Incidention() { end = null, start = null, distance = float.MaxValue };
            while (this.queue.Count > 0)
            {
                actNode = this.queue.Dequeue();
                foreach (var sib in actNode.Siblings)
                {
                    if (sib.Visited == TraversalState.Fresh)
                    {
                        sib.Visited = TraversalState.Open;
                        sib.Depth = actNode.Depth + 1;
                        sib.Predcessor = actNode;
                        this.queue.Enqueue(sib);
                        Incidention inc = new Incidention(actNode, sib, chPos);
                        if (inc.distance < minimalDist.distance && Math.Abs(actNode.Position.Y - sib.Position.Y) < 0.4f)  /// only horizontal lines
                        {
                            minimalDist = inc;
                        }
                    }
                }
                actNode.Visited = TraversalState.Closed;
            }

            if (minimalDist.distance < this.MaxCharacterDistance)
            {
                start = minimalDist.start;
                end = minimalDist.end;
            }
        }

        private void InitPaths()
        {
            foreach (var item in this)
            {
                item.DijkstraPredcessor = null;
                item.Distance = float.PositiveInfinity;
                
            }
        }

        private void Relax(WPNode u, WPNode v)
        {
            float dist = Vector2.Distance(u.Position, v.Position);
            if (v.Distance > u.Distance + dist)
            {
                v.Distance = u.Distance + dist;
                v.DijkstraPredcessor = u;
            }
        }

        /// <summary>
        /// Find paths with minimal distance
        /// </summary>
        /// <param name="first"></param>
        public void Dijkstra(WPNode first)
        {
            this.InitPaths();
            first.Distance = 0;
            PriorityQueue<WPNode> pqueue = new PriorityQueue<WPNode>(this.AllWaypoints.Count, new WPNodeDistanceComparer());
            
                pqueue.EnqueueRange(this);
            
            while (pqueue.Count > 0)
            {
                WPNode u = pqueue.DequeueMin();
                foreach (var sib in u.Siblings)
                {
                    Relax(u, sib);
                }
            }
        }

        public List<SerializableWPNode> Load(Stream input)
        {
            var ser = new XmlSerializer(typeof(List<SerializableWPNode>));
            return (List<SerializableWPNode>)ser.Deserialize(input);
        }

        public void Save(string waypointPath)
        {
            using (var fs = new FileStream(waypointPath, FileMode.Create, FileAccess.Write))
            {
                Save(fs);
            }
        }

        public void Save(Stream output)
        {
            var ser = new XmlSerializer(typeof(List<SerializableWPNode>));
            List<SerializableWPNode> serwpnodes = new List<SerializableWPNode>();
            foreach (var item in this.AllWaypoints.Values)
            {
                serwpnodes.Add(new SerializableWPNode(item));
            }
            ser.Serialize(output, serwpnodes);
        }

        public IEnumerator<WPNode> GetEnumerator()
        {
            return this.AllWaypoints.Values.GetEnumerator();
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return this.AllWaypoints.Values.GetEnumerator();
        }
    }
}